import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
* Classe que representa cliente
*/
public class Cliente implements Runnable
{    
    private HashMap<String,ArquivoLocal> listaArquivosLocal;
    private Service servidor;
    private String IP;
    private String diretorio;
    /**
    *
    * Função inicial do cliente, iniciar a thread 
    */
    public static void main(String[] args)
    {
        Cliente cliente = new Cliente(args[0],args[1], args[2], args[3]); //diretorio, interface, ipServidor, porta
	
        //abre terminal do cliente com as opçoes na tela
        Thread th = new Thread(cliente); //thread para executar a tranferencia de arquivos
        th.setName("compartilhador");
        th.setDaemon(true);   
        th.start();
        cliente.terminalOpcoes();
        th.interrupt();
    }
    /**
    *
    * Função onde o cliente verfica se o arquivo está disponível, e se estiver, disponibiliza e bloqueia
    */     
    public boolean travarArquivo(String nome)
    {
        if(this.listaArquivosLocal.containsKey(nome))
        {
            ArquivoLocal al = this.listaArquivosLocal.get(nome);
            if(al.getBloqueado())
            {
                return false;
            }
            else
            {
                synchronized(al)
                {
                    al.setBloqueado(true);
                }
                return true;
            }
        }
        else
        {
            return false;
        }
    }
    /**
    *
    * Função onde o cliente desbloqueia um arquivos, atualiza, e informa o servidor sobre a atualização
    */
    public boolean destravarArquivo(String nomeArquivo)
    {
        ArquivoLocal al =  this.listaArquivosLocal.get(nomeArquivo);
        File arquivo = new File(this.diretorio + nomeArquivo);
        synchronized(al)
        {
            al.setArquivo(arquivo);
            al.setVersao(arquivo.lastModified());
            al.setBloqueado(false);
        }
        try {
            this.servidor.atualizarArquivo(al);           
            return true;
        } catch (RemoteException ex) {
            ex.printStackTrace();
            System.out.println("Falha ao atualizar no servidor");
        }
        return false;
    }
    /**
    *
    * Metodo que atende as requisicoes de tranferencia de arquivos
    */
    public void run()
    {
        try{
            ArquivoInterface ai = new ArquivoImpl(this.diretorio, this);
            Naming.rebind("//localhost:21099/FileServer", ai);
            System.out.println("FileServer em execucao");
        }catch (Exception ex) 
        {
           System.out.println(ex);
           ex.printStackTrace();
        }
    }
    /**
    *
    * Função responsável pelo layout, e receber as requisições dos usuários
    */
    public void terminalOpcoes()
    {
        int opcao=1;
        Scanner scan = new Scanner(System.in);
        
        while(opcao != 0)
        {
            System.out.println("1-Obter lista de Arquivos no Servidor");
            System.out.println("2-Compartilhar Arquivo no Servidor");
            System.out.println("3-Descompartilhar Arquivo no Servidor");
            System.out.println("4-Exibir Estado do diretorio compartilhado");
            System.out.println("5-Verificar ultima atualizacao de arquivo");
            System.out.println("6-Solicitar leitura em arquivo de cliente");
            System.out.println("7-Solicitar escrita em arquivo de cliente");
            System.out.println("0-Sair");
            
            opcao = scan.nextInt();
            if(opcao == 1)
            {
                try{
                    this.exibirListaArquivos();
                } catch (RemoteException ex) 
                {
                    System.out.println("Erro na chamada remota");
                    Logger.getLogger(Cliente.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            else if(opcao == 2) //compartilha arquivo no servidor
            {
                System.out.println("Digite o nome do arquivo");
                String nomeArquivo = scan.nextLine();
                nomeArquivo = scan.nextLine();  
                try{
                    this.compartilhaArquivo(nomeArquivo);
            
                }catch(RemoteException ex)
                {
                    System.out.println("Erro na chamada remota");
                    System.out.println(ex);
                }
            }         
            else if(opcao ==3) //descompartilha arquivo no servidor
            {
                System.out.println("Digite o nome do arquivo");
                String nomeArquivo = scan.nextLine();
                nomeArquivo = scan.nextLine();
                try{
                    this.descompartilhaArquivo(nomeArquivo);

                }catch(RemoteException ex)
                {
                     System.out.println("Erro na chamada remota");
                     System.out.println(ex);
                }
            }
            else if(opcao == 4) //verifica estado do diretorio compartilhado
            {
                this.exibeEstadoDiretorio();
            }
            else if(opcao == 5) //verifica ultima atualizacao de arquivo no servidor, qualquer um que tem o arquivo faz isso
            {
            
            }
            else if(opcao == 6) //baixa arquivo do cliente
            {
                this.baixaArquivo();
            }
            
            else if(opcao ==7)
            {
                this.baixaArquivoEscrita();
            }
            
            try{
                this.atualizaEstadoDiretorioCompartilhado();
                
            }catch (RemoteException ex) 
            {
                System.out.println("Falha na atualizacao de arquivo no servidor: atualizaEstadoDiretorioCompartilhado");
                System.out.println(ex);
            }
            
            System.out.println("\n");
        }
        
    }
    /**
    *
    * Função onde o cliente solicita um arquivo de outro cliente para leitura
    */
    private boolean baixaArquivo()
    {
        Scanner scan = new Scanner(System.in);
        System.out.println("Digite o nome do arquivo:"); //pega dados do dono do arquivo
        String nomeArquivo = scan.nextLine();
        System.out.println("Nome da maquina que possui o arquivo:");
        String maquina = scan.nextLine();
        
        try{
            String name = "rmi://" +maquina +":21099/FileServer";
            System.out.println(name);            
            ArquivoInterface fi = (ArquivoInterface) Naming.lookup(name);
            byte[] filedata = fi.baixaArquivo(nomeArquivo, "");
            if(filedata ==null)
                return false;
            
            File file = new File(this.diretorio + nomeArquivo);
            file.setReadOnly();
            file.setWritable(true);
            
            BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(this.diretorio + file.getName()));
            output.write(filedata, 0, filedata.length);
            output.flush();
            output.close();
            Runtime.getRuntime().exec("chmod 555 "+ this.diretorio + file.getName());
            return true;
        }catch(Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
            return false;
        }
    }
    /**
    *
    * Função onde o cliente solicita um arquivo de outro cliente para escritas
    */
    private boolean baixaArquivoEscrita()
    {
        Scanner scan = new Scanner(System.in);
        System.out.println("Digite o nome do arquivo:"); //pega dados do dono do arquivo
        String nomeArquivo = scan.nextLine();
        System.out.println("Nome da maquina que possui o arquivo:");
        String maquina = scan.nextLine();
        
        try{
            String name = "rmi://" +maquina +":21099/FileServer";
            System.out.println(name);            
            ArquivoInterface fi = (ArquivoInterface) Naming.lookup(name);
            byte[] filedata = fi.baixaArquivo(nomeArquivo, this.IP);
            
            if(filedata ==null)
            {
                System.out.println("Arquivo bloqueado para escrita");
                return false;
            }
            
            File file = new File(this.diretorio + nomeArquivo);
            BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(this.diretorio + file.getName()));
            output.write(filedata, 0, filedata.length);
            output.flush();
            output.close();
            Runtime.getRuntime().exec("chmod 777 "+ this.diretorio + file.getName());
            
            Thread.sleep(60*1000); // tempo de alteração do arquivo     
            //altera o arquivo
            FileWriter fw = new FileWriter(file, true);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write("Arquivo alterado por: "+ this.IP);
            bw.newLine();
            bw.close();
            fw.close();
            
            //enviando de volta para  o cliente de origem
            byte buffer[] = new byte[(int)file.length()];
            BufferedInputStream input = new BufferedInputStream(new FileInputStream(this.diretorio +nomeArquivo));
            input.read(buffer, 0, buffer.length);
            input.close();  
            fi.escreveArquivo(nomeArquivo, buffer); //devolve arquivo para dono arquivo    
            Runtime.getRuntime().exec("chmod 555 "+ this.diretorio + file.getName());
            return true;
        }catch(Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
            return false;
        }
    }
    /**
    *
    * Função para exibir os detalhes do arquivo como string
    */
    private void exibeEstadoDiretorio() //exibe os arquivos no diretorio compartilhado
    {
        Collection<ArquivoLocal> co = this.listaArquivosLocal.values();
        Iterator<ArquivoLocal> it = co.iterator();
        
        while(it.hasNext())
        {
            ArquivoLocal al = it.next();
            System.out.println("Nome: " +al.getNome()+ " Versao: "+al.getVersao()+ " Compartilhado: "+ al.isCompartilhado() );
        }       
    }
    /**
    *
    * Função para atualizar as informações do cliente
    */
    private void atualizaEstadoDiretorioCompartilhado()  throws RemoteException
    {
        File diretorio = new File(this.diretorio);
        if( diretorio.exists() )
        {
            if(diretorio.isFile())
            {
                System.out.println("Diretório invalido"); 
            } 
            else 
            {
                String[] arquivos = diretorio.list(); //pega todos os arquivos no diretorio
                for( String arquivo : arquivos) //para cada arquivo que tem no diretorio
                {
                    File arquivo_list = new File(this.diretorio+arquivo);
                    if(this.listaArquivosLocal.containsKey(arquivo_list.getName())) //se tem o arquivo na lista verifica ultima atualizacao e atualiza no servidor se ele esta compartilhado
                    {
                        ArquivoLocal arq = this.listaArquivosLocal.get(arquivo_list.getName()); 
                        if(arq.isCompartilhado()) //se o arquivo esta compartilhado
                        {
                            if(arq.getVersao() != arquivo_list.lastModified()) //se o arquivo esta atualizado
                            {
                                //atualiza versao do arquivo na lista local
                                arq.setVersao(arquivo_list.lastModified());
                                //atualiza versao do arquivo no servidor
                                this.servidor.atualizarArquivo(arq);                                
                            }
                        }
                    }
                    else //se nao tem, adiciona na lista local = arquivo novo adicionado no diretorio
                    {
                        ArquivoLocal arq = new ArquivoLocal(arquivo_list.getName(), arquivo_list.lastModified(), 
                            this.IP, arquivo_list, false);
                        
                        this.listaArquivosLocal.put(arq.getNome(), arq);
                    }
                }
            }
        }
    }
    /**
    *
    * Função para imprimir todos os arquivos disponiveis no Servidor
    */
    private void exibirListaArquivos() throws RemoteException
    {
        for (Iterator<ArquivoLocal> it = this.servidor.obterListaArquivos().values().iterator(); it.hasNext(); ) 
        {
             ArquivoLocal arquivo = it.next();
             if(arquivo != null)
             System.out.println("Nome: "+ arquivo.getNome()+" IP: "+ arquivo.getCliente_owner_ip() +
                     " versao: " + arquivo.getVersao());
        }
    }
    /**
    *
    * Função para compartilhar um arquivo para o servidor
    */
    private void compartilhaArquivo(String nomeArquivo) throws RemoteException
    {
        boolean var;
        
        if(this.listaArquivosLocal.containsKey(nomeArquivo))
        {
            var = this.servidor.compartilharArquivo(this.listaArquivosLocal.get(nomeArquivo));
        
            if(var)
            {
                System.out.println("Arquivo compartilhado com sucesso");
                this.listaArquivosLocal.get(nomeArquivo).setCompartilhado(true);
            }
            else
            {
                System.out.println("Arquivo nao foi compartilhado");
            }
        }
        else
        {
            System.out.println("Arquivo nao encontrado no diretorio: " + this.diretorio);
        }
    }
    /**
    *
    * Função para descompartilhar arquivo no servidor
    */
    private void descompartilhaArquivo(String nomeArquivo) throws RemoteException
    {
        boolean var;
        if(this.listaArquivosLocal.containsKey(nomeArquivo)) //se o arquivo existe
        {
            var = this.servidor.descompartilharArquivo(this.listaArquivosLocal.get(nomeArquivo));
            if(var)
            {
                System.out.println("Arquivo descompartilhado com sucesso");
                this.listaArquivosLocal.get(nomeArquivo).setCompartilhado(false);
            }
            else
            {
                System.out.println("Arquivo nao foi descompartilhado");
            }
        }
        else
        {
            System.out.println("Arquivo nao encontrado no diretorio: " + this.diretorio);
        }
    }
    /**
    *
    * Função construtor, e iniciar o serviço que o cliente irá disponibilizar
    */
    public Cliente(String caminho, String interfac, String ipServidor, String porta)
    {
        //seta ip da maquina cliente
        this.IP = interfac;
        //pega todos os arquivos em caminho
        this.diretorio = caminho;
        this.listaArquivosLocal = new HashMap<String, ArquivoLocal>();
        File diretorio = new File(caminho);
        if( diretorio.exists() )
        {
            if(diretorio.isFile())
            {
                System.out.printf("Diretório invalido\n"); 
            } 
            else 
            {
                String[] arquivos = diretorio.list(); //pega todos os arquivos no diretorio passado em args[0]
                for( String arquivo : arquivos)
                {
                    File arquivo_list = new File(caminho+arquivo);
                    ArquivoLocal arquivolocal = new ArquivoLocal(arquivo_list.getName(), arquivo_list.lastModified(), this.IP, arquivo_list, false);
                    listaArquivosLocal.put(arquivo_list.getName(), arquivolocal);
                }
            }
        }
        //conecta com o servidor
        try
        {
            String conexao = "rmi://"+ ipServidor + ":" + porta + "/Servidor";
            System.out.println("ConexaoCliente: "+conexao); //ok, conexao ok
            this.servidor = (Service) Naming.lookup(conexao);
            //this.servidor = (Service) Naming.lookup("rmi://localhost:11099/Servidor");
            
        }catch (Exception e) 
        {
            System.out.println(e);
            System.out.println("Erro ao conectar no servidor pelo cliente");
        }
    }
    
}
